type ListEdit[T] protected {
    data:     Reactive[List[T]],
    buffer:   Mutable[List[(T,ListEditKey)]],
    next-key: Mutable[ListEditKey],
    mutating: Mutable[Bool]
};
method ListEdit.underlying-data: Reactive[List[T]]
    &(edit) => edit.data;

type ListEditKey protected
    Number;

type ListEditItemInfo {
    index: Number,
    total: Number
};
method ListEditItemInfo.is-first: Bool
    &(info) => (info.index = 0);
method ListEditItemInfo.is-last: Bool
    &(info) => ((info.index + 1) = info.total);

type ListEditItemTransformer[T,X]
    &(ListEditKey,Computed[ListEditItemInfo],Reactive[T]) => Computed[X];


// constructor

export function ListEdit:[T]
    &(Reactive[List[T]]) => Sync[ListEdit[T]]
    &(data) =>
        \ do let buffer := { Mutable::[List[(T,ListEditKey)]] [] },
        \ do let next-key := { Mutable::[ListEditKey] { |ListEditKey| 0 } },
        \ do let mutating := { Mutable::[Bool] No },
        let edit := { |ListEdit| {
            data,
            buffer,
            next-key,
            mutating
        } },
        ({ return edit } with
            \ -> let new-list := data.out,
            \ do let mutating := edit.mutating.read,
            if mutating:
                { edit.mutating.write No },
            else:
                let L := new-list.{length},
                \ do let base := edit.next-key.read,
                \ do { edit.next-key.write (base + L).{|ListEditKey|} },
                { buffer.write
                    new-list
                    . { Seq }
                    . { with-index }
                    . { map &(value,index) =>
                        (value, (base + index).{|ListEditKey|}) }
                    . { List }
                });


// Private Utility Methods

method ListEdit.mutate:
    &(&(List[(T,ListEditKey)]) => Sync[List[(T,ListEditKey)]]) => Sync
    &(edit) => &(f) =>
        \ do let list := edit.buffer.read,
        \ do let new-list := { f list },
        \ do { edit.buffer.write new-list },
        \ do { edit.mutating.write Yes },
        \ do (edit.data.in <- (new-list map &(value,_) => value)),
        { return () };

method ListEdit.gen-key:
    Sync[ListEditKey]
    &(edit) =>
        \ do let key := edit.next-key.read,
        \ do { edit.next-key.write (key + 1).{|ListEditKey|} },
        { return key };


// State Output

export method ListEdit.state:
    Computed[List[(T,ListEditKey)]]
    &(edit) =>
        ((edit.data.out with-value-from edit.buffer)
            map &(_,bufval) => bufval);

export function transform:[T,X]
    &(ListEdit[T], ListEditItemTransformer[T,X]) => Computed[List[X]]
    &(edit, f) =>
        \ do let @state := (edit.state memo-map &(list) =>
            let total := list.length,
            let keys := (list map &(_,key) => key),
            (keys, { Map { List list.{Seq}.{with-index}.{ map &(item,index) =>
                let (value, key) := item,
                let info := { |ListEditItemInfo| { index, total } },
                (key, { value, info })
            } } }) ),
        let @keys := (@state map &(keys,_) => keys),
        let @map := (@state map &(_,map) => map),
        (@keys list-map &(k) =>
            let in := { Sink::[T] &(v) => { edit.assign (k, v) } },
            let out := (@map map |.{ get k }.{!!}.value|),
            let i := (@map map |.{ get k }.{!!}.info|),
            let r := { Reactive { in, out } },
            { f (k, i, r) }
        );


// Edit Operations

export method ListEdit.assign:
    &(ListEditKey, T) => Sync
    &(edit) => &(target, new-value) =>
        { edit.mutate &(list) =>
            { return (list map &(old-value, key) =>
                if (key = target):
                    (new-value, key),
                else:
                    (old-value, key)
            ) } };

export method ListEdit.append:
    &(T) => Sync
    &(edit) => &(new-value) =>
        { edit.mutate &(list) =>
            \ do let key := edit.gen-key,
            { return { concat [list, [(new-value, key)]] } } };

export method ListEdit.prepend:
    &(T) => Sync
    &(edit) => &(new-value) =>
        { edit.mutate &(list) =>
            \ do let key := edit.gen-key,
            { return { concat [[(new-value, key)], list] } } };

export method ListEdit.insert-after:
    &(ListEditKey, T) => Sync
    &(edit) => &(pivot, new-value) =>
        { edit.mutate &(list) =>
            \ do let new-key := edit.gen-key,
            { return (list flat-map &(existing) =>
                let (_, existing-key) := existing,
                if (existing-key = pivot):
                    [existing, (new-value,new-key)],
                else:
                    [existing]
                ) } };

export method ListEdit.insert-before:
    &(ListEditKey, T) => Sync
    &(edit) => &(pivot, new-value) =>
        { edit.mutate &(list) =>
            \ do let new-key := edit.gen-key,
            { return (list flat-map &(existing) =>
                let (_, existing-key) := existing,
                if (existing-key = pivot):
                    [(new-value,new-key), existing],
                else:
                    [existing]
                ) } };

export method ListEdit.delete:
    &(ListEditKey) => Sync
    &(edit) => &(target) =>
        { edit.mutate &(list) =>
            { return (list filter &(_,key) => (key != target)) } };

export method ListEdit.move-after:
    &(ListEditKey, ListEditKey) => Sync
    &(edit) => &(pivot, target) =>
        { edit.mutate &(list) =>
            let target-item :=
                (list flat-map &(item) =>
                    let (_, key) := item,
                    if (key = target): [item], else: []
                ),
            { return (list flat-map &(item) =>
                let (_, key) := item,
                if (key = pivot): { concat [[item], target-item] },
                elif (key = target): [],
                else: [item]
            )} };

export method ListEdit.move-before:
    &(ListEditKey, ListEditKey) => Sync
    &(edit) => &(pivot, target) =>
        { edit.mutate &(list) =>
            let target-item :=
                (list flat-map &(item) =>
                    let (_, key) := item,
                    if (key = target): [item], else: []
                ),
            { return (list flat-map &(item) =>
                let (_, key) := item,
                if (key = pivot): { concat [target-item, [item]] },
                elif (key = target): [],
                else: [item]
            )} };

function swap:
    [T] &(List[(T,ListEditKey)], Maybe[((T,ListEditKey),(T,ListEditKey))]) => List[(T,ListEditKey)]
    &(list, pair?) =>
        switch pair?:
            case Some (i1, i2):
                let (_, k1) := i1,
                let (_, k2) := i2,
                (list map &(item) =>
                    let (_, key) := item,
                    if (key = k1): i2,
                    elif (key = k2): i1,
                    else: item
                ),
            default:
                list,
        end;

export method ListEdit.swap:
    &(ListEditKey, ListEditKey) => Sync
    &(edit) => &(target1, target2) =>
        { edit.mutate &(list) =>
            let pair? :=
                \ Maybe let target1-item :=
                    (list find-first &(_,key) => (key = target1)),
                \ Maybe let target2-item :=
                    (list find-first &(_,key) => (key = target2)),
                { Some (target1-item, target2-item) },
            { return { swap (list, pair?) } } };

export method ListEdit.swap-down:
    &(ListEditKey) => Sync
    &(edit) => &(target) =>
        { edit.mutate &(list) =>
            let a := ((list.{Seq} map |.{Some}|) prepend None),
            let b := list.{Seq},
            let pair? :=
                ((a zip b) get-first &(prev?, cur) =>
                    \ Maybe let prev := prev?,
                    let (_, prev-key) := prev,
                    if (prev-key = target):
                        { Some (prev, cur) },
                    else:
                        None
                ),
            { return { swap (list, pair?) } } };

export method ListEdit.swap-up:
    &(ListEditKey) => Sync
    &(edit) => &(target) =>
        { edit.mutate &(list) =>
            let a := ((list.{Seq} map |.{Some}|) prepend None),
            let b := list.{Seq},
            let pair? :=
                ((a zip b) get-first &(prev?, cur) =>
                    let (_, cur-key) := cur,
                    if (cur-key = target):
                        (prev? map &(prev) => (prev, cur)),
                    else:
                        None
                ),
            { return { swap (list, pair?) } } };
